/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 *
 */

import path from 'path';

import { hvigorCore, TaskDetails } from '@ohos/hvigor';
import {
  build,
  BuildType,
  CompileInnerParam,
  DependentModuleInfoV2,
  ProjectConfigEvolution,
  ProjectConfigEvolutionBuilder
} from '@ohos/hvigor-arkts-compose';
import fse from "fs-extra";
import { Level, levels } from 'log4js';

import { getCurrentProductName } from '../../../common/find-target-product.js';
import { BuildArtifactConst, BuildDirConst } from '../../../const/build-directory-const.js';
import { ArkTSVersionConst, CommonConst } from '../../../const/common-const.js';
import { CodeType } from '../../../enum/code-type-enum.js';
import { mapStringToModuleTypeEnum } from '../../../enum/module-type-enum.js';
import { Dependency, DependencyType } from '../../../project/dependency/core/dependency-interface.js';
import { TargetTaskService } from '../../../tasks/service/target-task-service.js';
import { OhosHapTask } from '../../../tasks/task/ohos-hap-task.js';
import { getAppInfoByProject } from '../../../utils/integrated-hsp-utils.js';
import { handleLogs, OhosLogger } from '../../../utils/log/ohos-logger.js';
import { EvolutionService } from '../../service/evolution-service.js';
import { ServiceLocator } from '../../service/service-locator.js';


let level: Level = levels.DEBUG;

/**
 * 编译arkTS 2.0源码
 *
 * @since 2025/1/24
 */
export abstract class AbstractCompileArktsEvolution extends OhosHapTask {
  protected readonly aceModuleJsonPath: string | undefined;
  protected readonly resourceTable: string | undefined;
  protected readonly rawFileResource: string | undefined;
  protected readonly evolutionService: EvolutionService;

  public logger: OhosLogger = OhosLogger.getLogger(AbstractCompileArktsEvolution.name);

  constructor(taskService: TargetTaskService, taskDetails: TaskDetails) {
    super(taskService, taskDetails);
    this.evolutionService = ServiceLocator.getEvolutionService();

    this.aceModuleJsonPath = this.pathInfo.getIntermediatesArkModuleJsonPath();
    if (!fse.existsSync(this.aceModuleJsonPath)) {
      this.aceModuleJsonPath = path.resolve(this.pathInfo.getIntermediatesProcessProfile());
    }
    this.resourceTable = path.resolve(this.pathInfo.getIntermediatesRes(), BuildArtifactConst.RESOURCE_TABLE_TXT);
    this.rawFileResource = path.resolve(this.pathInfo.getIntermediatesRes(), BuildDirConst.RESTOOL_BUILD_RESOURCES, BuildDirConst.RAW_FILE);
  }

  protected async buildProjectConfigEvolutionBuilder(): Promise<ProjectConfigEvolutionBuilder> {
    const projectConfig: ProjectConfigEvolution = {
      plugins: {
        ArkUI: this.sdkInfo.getArkUIPlugin(),
        'ArkUI-Memo': this.sdkInfo.getArkUIMemoPlugin(),
      },
      compileFiles: await this.getCompileFiles(),
      buildMode: this.targetService.getBuildMode(),
      projectRootPath: this.projectModel.getProjectDir(),
      moduleRootPath: this.moduleModel.getModule().getNodeDir(),
      cachePath: path.resolve(this.getTaskTempDir(this.targetData), this.targetService.getBuildMode().toLowerCase()),
      loaderOutPath: path.resolve(this.pathInfo.getInterMediatesLoaderOutPath(), CodeType.ETS),
      compileSdkVersion: this.compileApiVersion,
      compatibleSdkVersion: this.compatibleApiVersion,
      aceModuleJsonPath: this.aceModuleJsonPath!,
      appResource: this.resourceTable!,
      rawFileResource: this.rawFileResource!,
      bundleName: (() => {
        const { bundleName } = getAppInfoByProject(this.projectModel);
        return bundleName;
      })(),
      useNormalizedOHMUrl: this.targetService.getBuildOption()?.strictMode?.useNormalizedOHMUrl ?? false,
      buildType: BuildType.BUILD,
      packageName: this.moduleName,
      buildSdkPath: this.sdkInfo.getDriverBuild(),
      sourceRoots: ['./'],
      moduleType: this.moduleModel.getModuleType(),
      moduleName: this.moduleName,
      dependentModuleList: this.collectDependentModuleList(),
      hasMainModule: true,
      buildLoaderJson: path.resolve(this.pathInfo.getIntermediatesLoaderPath(), BuildArtifactConst.LOADER_JSON),
      integratedHsp: this.moduleModel.isHspModule() ? this.targetService.getBuildOption().arkOptions?.integratedHsp ?? false : false,
      allowEmptyBundleName: hvigorCore.getParameter().getProperty(CommonConst.EMPTY_BUNDLENAME) ?? false,
      declgenV2OutPath: this.pathInfo.getDeclgenV2OutPath(),
      externalApiPaths: this.evolutionService.getLoadExternalApiPaths(this.targetData.isHarmonyOS(), this.sdkInfo),
      deviceTypes: this.moduleModel.getDeviceTypes(),
      permissions: this.moduleModel.getPermission(),
      byteCodeHar: this.targetService.isByteCodeHar(),
    };

    const innerParam: CompileInnerParam = {
      level: this.logger.getLevel() as Level,
    };

    return {
      ...projectConfig,
      ...innerParam
    };
  }

  /**
   * 收集依赖的模块信息
   * @protected
   */
  protected collectDependentModuleList(): DependentModuleInfoV2[] {
    const dependentModuleList: DependentModuleInfoV2[] = [];

    const current = this.service.getDependencyInfo().getModulePkgNode().npm;
    dependentModuleList.push(this.collectLocalModuleInfo(current));
    this.service.getAllDependencies().forEach((dependency) => {
      if (dependency.isHspDependency()) {
        return;
      }
      if (dependency.isLocal()) {
        dependentModuleList.push(this.collectLocalModuleInfo(dependency));
      } else {
        dependentModuleList.push(this.collectRemoteModuleInfo(dependency));
      }
    });
    return dependentModuleList;
  }

  /**
   * 收集本地模块信息
   * @param dependency
   */
  collectLocalModuleInfo(dependency: Dependency): DependentModuleInfoV2 {
    const modulePathInfo = ServiceLocator.getModulePathInfoServiceByProductAndModule(getCurrentProductName(), dependency.getModuleName());
    return {
      language: ServiceLocator.getModuleLanuage(dependency.getModuleName()),
      packageName: dependency.getPackageName(),
      moduleType: mapStringToModuleTypeEnum(dependency.getDependencyType())!,
      moduleName: dependency.getModuleName(),
      modulePath: dependency.getDependencyRootPath(),
      entryFile: path.resolve(dependency.getDependencyRootPath(), dependency.getDependencyMainFileRelativePath() ?? BuildArtifactConst.DEFAULT_INDEX_ETS),
      declFilesPath: modulePathInfo?.getDeclFileInfoJsonPath(),
      declgenV1OutPath: modulePathInfo?.getDeclgenV1OutPath(),
      declgenV2OutPath: modulePathInfo?.getDeclgenV2OutPath(),
      declgenBridgeCodePath: modulePathInfo?.getDeclgenBridgeCodePath(),
      sourceRoots: ['./'],
      dependencies: Object.keys(dependency.getDependencies()),
    };
  }

  /**
   * 收集三方包的信息
   * @param dependency
   */
  collectRemoteModuleInfo(dependency: Dependency): DependentModuleInfoV2 {
    const projectPath = this.projectModel.getProject().getNodeDir();
    const packageName = dependency.getPackageName();
    return {
      language: "1.2",
      packageName: dependency.getPackageName(),
      moduleType: mapStringToModuleTypeEnum(dependency.getDependencyType())!,
      moduleName: dependency.getModuleName(),
      modulePath: dependency.getDependencyRootPath(),
      entryFile: path.resolve(dependency.getDependencyRootPath(), dependency.getDependencyMainFileRelativePath() ?? BuildArtifactConst.DEFAULT_INDEX_ETS),
      declFilesPath: this.pathInfo.getRemoteDependencyDeclFileInfoJsonPath(projectPath, packageName),
      declgenV1OutPath: this.pathInfo.getRemoteDependencyDeclgenV1OutPath(projectPath, packageName),
      declgenV2OutPath: this.pathInfo.getRemoteDependencyDeclgenV2OutPath(projectPath, packageName),
      declgenBridgeCodePath: this.pathInfo.getRemoteDependencyDeclgenBridgeCodePath(projectPath, packageName),
      sourceRoots: ['./'],
      dependencies: Object.keys(dependency.getDependencies()),
      byteCodeHar: dependency.isByteCodeHarDependency(),
      abcPath: path.join(dependency.getDependencyRootPath(), CodeType.ETS, BuildArtifactConst.MODULES_STATIC_ABC),
    };
  }

  /**
   * 收集依赖的所有模块的源码信息
   * @protected
   */
  protected async getCompileFiles(): Promise<string[]> {
    const compileFiles: string[] = [];
    const ignoreDirs = ['src/ohosTest', 'src/test' , 'build', 'preview', 'test', 'oh_modules'];

    // 当前是1.2的模块才收集本模块的文件
    if (this.targetService.getSelfArkTSVersion() === ArkTSVersionConst.ARKTS1_2) {
      compileFiles.push(...await this.getModuleCodeInfo(this.service.getDependencyInfo().getModulePkgNode().npm,
        this.targetName === 'ohosTest'));
    }

    const dependency = this.service.getDependencyInfo();
    for (const dep of dependency.getAllDependencies()) {
      if (dep.getDependencyType() === DependencyType.DEPENDENCY_TYPE_HSP) {
        continue;
      }

      if (this.service.getDependencySelfArkTSVersion(dep) !== ArkTSVersionConst.ARKTS1_2) {
        continue;
      }
      compileFiles.push(...await this.getModuleCodeInfo(dep));
    }

    return compileFiles;
  }

  async getModuleCodeInfo(dep: Dependency, isCollectOhosTest = false) {
    const compileFiles = [];
    // 收集依赖的代码列表
    const depSourceRoot = path.dirname(dep.getPackageFilePath());
    let soucePrefix = path.resolve(depSourceRoot, 'src', 'main');
    if (isCollectOhosTest) {
      soucePrefix = path.resolve(depSourceRoot, 'src', 'ohosTest');
    }
    const ignoreDirs = ['src/test' , 'build', 'preview', 'test', 'oh_modules'];

    const depSourceCodeFiles = await this.findEtsFiles(soucePrefix, ignoreDirs);
    compileFiles.push(...depSourceCodeFiles);

    const packageJsonObj = dep.getPackageJsonObj();
    // 收集oh-package.json5中配置的main
    if (packageJsonObj.main && fse.existsSync(path.resolve(dep.getDependencyRootPath(), packageJsonObj.main))) {
      const mainFile = path.resolve(dep.getDependencyRootPath(), packageJsonObj.main);
      if (!mainFile.endsWith('.d.ets')) {
        compileFiles.push(mainFile);
      }
    }

    if (packageJsonObj.types && fse.existsSync(path.resolve(dep.getDependencyRootPath(), packageJsonObj.types))) {
      const typesFile = path.resolve(dep.getDependencyRootPath(), packageJsonObj.types);
      if (!typesFile.endsWith('.d.ets')) {
        compileFiles.push(typesFile);
      }
    }
    return compileFiles;
  }

  async findEtsFiles(dir: string, ignoreDirs: string[] = [],
    rootPath: string = dir): Promise<string[]> {
    return this.findFiles(dir, ignoreDirs, rootPath, (file: string) => file.endsWith('ets') && !file.endsWith('.d.ets'));
  }

  async findFiles(dir: string, ignoreDirs: string[] = [], rootPath: string = dir, fileter: (file: string) => boolean) {
    const fileList: string[] = [];
    const files = await fse.readdir(dir);

    for (const file of files) {
      const filePath = path.join(dir, file);
      const stat = await fse.stat(filePath);

      if (stat.isDirectory()) {
        // 检查当前目录的完整路径是否包含忽略列表中的路径
        const normalizedPath = path.normalize(filePath);
        const shouldIgnore = ignoreDirs.some((ignoreDir) => {
          const normalizedIgnoreDir = path.normalize(path.resolve(rootPath, ignoreDir));
          return normalizedPath.includes(normalizedIgnoreDir);
        });

        if (shouldIgnore) {
          continue;
        }

        const subDirFiles = await this.findFiles(filePath, ignoreDirs, rootPath, fileter);
        fileList.push(...subDirFiles);
      } else if (stat.isFile() && !file.startsWith('BuildProfile') && fileter(file)) {
        fileList.push(filePath);
      }
    }
    return fileList;
  }

  /**
   * 执行构建
   * @param projectConfig
   * @param log
   * @protected
   */
  protected async runBuild(projectConfig: ProjectConfigEvolutionBuilder, log: OhosLogger) {
    level = levels.getLevel(projectConfig.level!, levels.INFO);
    log.setLevel(level);
    const result = await build(projectConfig);
    handleLogs(result.compileLogEvents, log, level);
    if (!result.isSuccess) {
      throw result.error;
    }
  }
}
